use std::collections::HashMap;

use crate::http::{HttpMethod, HttpRequest, HttpResponse, HttpUri};
use super::state::HttpStateContainer;

#[derive(Debug)]
pub enum HttpRouteSegment {
    Static(String),
    Param(String),
}

#[derive(Debug)]
pub struct HttpRouterInfo {
    pub method: HttpMethod,
    pub segments: Vec<HttpRouteSegment>,
    pub use_body: bool,
}

#[async_trait::async_trait]
pub trait HttpRouter {
    fn route_info(&self) -> &HttpRouterInfo;
    async fn handle(&self, path_args: HashMap<String, String>, query_args: &HashMap<String, String>, body: Option<Vec<u8>>, states: &HttpStateContainer) -> HttpResponse;

    async fn route(&self, request: &HttpRequest, method: HttpMethod, uri: &HttpUri, states: &HttpStateContainer) -> Option<HttpResponse> {
        // Extract uri arguments from passing paths!
        let path_args = self.route_info().route_match(method, &uri.path)?;

        if self.route_info().use_body {
            // Use body, so fetch the bytes strams
            let body = match request.body.as_ref() {
                Some(body) => { body.data.clone() }
                None => Vec::new(),
            };

            Some(self.handle(path_args, &uri.query, Some(body), states).await)
        } else {
            Some(self.handle(path_args, &uri.query, None, states).await)
        }
    }
}

impl HttpRouterInfo {
    pub fn route_match(&self, method: HttpMethod, paths: &[String]) -> Option<HashMap<String, String>> {
        if self.method != method {
            return None;
        }
        if paths.len() != self.segments.len() {
            return  None;
        }
        
        let mut uri_params = HashMap::<String, String>::new();
        for (segment, path) in self.segments.iter().zip(paths) {
            match segment {
                HttpRouteSegment::Static(seg_path) => if seg_path != path { return None; },
                HttpRouteSegment::Param(param_name) => { uri_params.insert(param_name.into(), path.into()); },
            }
        }

        Some(uri_params)
    }
}
